use crate::core::operation::build_target::BuildTarget;
use crate::core::blueprint::build_coffee_tales::Error::GetRepositoryName;
use crate::core::blueprint::Blueprint;
use crate::core::error::blueprint_error::BlueprintError;
use crate::core::error::not_found_error::NotFoundError;
use crate::core::operation::change_unity_symbol::ChangeUnitySymbol;
use crate::core::operation::git::Git;
use crate::core::operation::unity::Unity;
use crate::core::path_type::TypedPath;
use crate::core::path_type::TypedPath::Contextual;
use grpc_server::build_coffee_tales_parameter::{self, Channel, CodeLevel};
use grpc_server::BuildCoffeeTalesParameter;
use crate::core::util::Util;
use lazy_static::lazy_static;
use prost::UnknownEnumValue;
use snafu::{ResultExt, Snafu};
use std::path::Path;
use crate::core::services::job::job::{Job, JobBuilder};

#[derive(Debug, Snafu)]
#[snafu(visibility(pub(crate)))]
pub enum Error {
    #[snafu(display("The channel with value {channel} is not supported"))]
    NotSupportChannel {
        channel: i32,
        source: UnknownEnumValue,
    },

    #[snafu(display("The code level with value {code_level} is not supported"))]
    NotSupportCodeLevel {
        code_level: i32,
        source: UnknownEnumValue,
    },

    // #[snafu(transparent)]
    FieldNotFound {
        source: NotFoundError,
    },

    #[snafu(display("The host is not MacOs, can not build Coffee Tales for iOS"))]
    BuildIosWithoutMacOs {},

    #[snafu(display("Failed to extract name of repository from {url}"))]
    GetRepositoryName {
        url: String,
    },
}

impl From<Error> for BlueprintError {
    fn from(value: Error) -> Self {
        BlueprintError::BuildAndroidCoffeeTales { source: value }
    }
}

struct AndroidExportConfig {
    download_resource_url: Option<String>,
    max_resource_size: Option<u64>,
    version: String,
    export_apk: bool,
    export_aab: bool,
}

struct IOSExportConfig {
    download_resource_url: Option<String>,
    version: String,
}

enum BuildTargetConfig {
    Android(Option<AndroidExportConfig>),
    IOS(Option<IOSExportConfig>),
}

pub struct BuildAndroidCoffeeTales {
    repo_url: String,
    branch: String,
    channel: Channel,
    code_level: CodeLevel,
    upload_resource_url: Option<String>,
    build_target: BuildTargetConfig,
}

lazy_static! {
    static ref PROJECT_SETTING_PATH: TypedPath =
        Contextual(r"project_x\ProjectSettings\ProjectSettings.asset".to_string());
}

impl BuildAndroidCoffeeTales {
    pub fn new(parameter: BuildCoffeeTalesParameter) -> Result<Self, Error> {
        let n = Self {
            repo_url: parameter.repo_url,
            branch: parameter.branch,
            channel: parameter
                .channel
                .try_into()
                .context(NotSupportChannelSnafu {
                    channel: parameter.channel,
                })?,
            code_level: parameter
                .code_level
                .try_into()
                .context(NotSupportCodeLevelSnafu {
                    code_level: parameter.code_level,
                })?,
            upload_resource_url: parameter.upload_resource_url,
            build_target: {
                let build_target = parameter
                    .build_target
                    .ok_or(NotFoundError::Field {
                        filed_name: "build_target".to_string(),
                    })
                    .context(FieldNotFoundSnafu {})?;

                match build_target {
                    build_coffee_tales_parameter::BuildTarget::Android(android) => {
                        let c = android.export_config.map(|e| AndroidExportConfig {
                            download_resource_url: e.download_resource_url,
                            max_resource_size: e.max_resource_size,
                            version: e.version,
                            export_apk: e.apk,
                            export_aab: e.aab,
                        });
                        BuildTargetConfig::Android(c)
                    }
                    build_coffee_tales_parameter::BuildTarget::Ios(ios) => {
                        let c = ios.export_config.map(|e| IOSExportConfig {
                            download_resource_url: e.download_resource_url,
                            version: e.version,
                        });

                        BuildTargetConfig::IOS(c)
                    }
                }
            },
        };

        Ok(n)
    }

    fn get_build_target(&self) -> BuildTarget {
        match self.build_target {
            BuildTargetConfig::Android(_) => BuildTarget::Android,
            BuildTargetConfig::IOS(_) => BuildTarget::IOS,
        }
    }

    fn get_clone_client_step(&self) -> Result<Git, Error> {
        new_git_step(&self.repo_url, &self.branch)
    }

    fn get_change_channel_symbol_step(&self) -> Result<ChangeUnitySymbol, Error> {
        let step = ChangeUnitySymbol::new(
            &PROJECT_SETTING_PATH,
            self.get_build_target(),
            match self.channel {
                Channel::Null => vec![],
                Channel::DianHun => vec!["DIAN_HUN"],
                Channel::Coconut => vec!["CIS"],
                Channel::QooApp => vec!["QOO_APP"],
                Channel::GooglePlay => vec!["GOOGLE_PLAY"],
                Channel::AppStore => vec!["APP_STORE"],
            }
            .into_iter(),
            vec![].into_iter(),
        );
        Ok(step)
    }

    fn get_code_level_symbol_step(&self) -> Result<ChangeUnitySymbol, Error> {
        let step = ChangeUnitySymbol::new(
            &PROJECT_SETTING_PATH,
            self.get_build_target(),
            match self.code_level {
                CodeLevel::Publish => {
                    vec![]
                }
                CodeLevel::Stage => {
                    vec!["SORANI_STAGE"]
                }
                CodeLevel::Debug => {
                    vec!["SORANI_STAGE", "SORANI_DEBUG"]
                }
            }
            .into_iter(),
            match self.code_level {
                CodeLevel::Publish => {
                    vec!["SORANI_STAGE", "SORANI_DEBUG"]
                }
                CodeLevel::Stage => {
                    vec!["SORANI_DEBUG"]
                }
                CodeLevel::Debug => {
                    vec![]
                }
            }
            .into_iter(),
        );
        Ok(step)
    }

    fn get_run_unity_step(&self) -> Result<Unity, Error> {
        let unity = Unity::new(
            self.get_build_target(),
            TypedPath::Normal(Path::new(r"C:\Users\22644\projects\project_x").to_path_buf()),
            "DevelopTools.Test".to_string(),
            Some(TypedPath::Normal(
                Path::new(r"C:\Users\22644\projects\project_x\log_from_parrot.txt").to_path_buf(),
            )),
            None,
        );
        Ok(unity)
    }
}

impl Blueprint for BuildAndroidCoffeeTales {
    fn build(&self, id: usize) -> Result<Job, BlueprintError> {
        let job = JobBuilder::new(id)
            .set_workspace(Util::user_dirs().home_dir().join("projects"))
            .add_step(self.get_clone_client_step()?)
            .add_step(self.get_change_channel_symbol_step()?)
            .add_step(self.get_code_level_symbol_step()?)
            .add_step(self.get_run_unity_step()?)
            .build();
        Ok(job)
    }
}
fn new_git_step(repo_url: &str, branch: &str) -> Result<Git, Error> {
    let repo_name = Util::get_git_repo_name(&repo_url).ok_or(GetRepositoryName {
        url: repo_url.to_string(),
    })?;
    let git = Git::new(&repo_url, &Contextual(repo_name), &branch);
    Ok(git)
}
